--------------------------------------------------------------------------------
--  @file:  function.lua
-- 	Time: 	2019-09-27 15:39:33
-- 	Author:	KJOMOCGTEXG2ERC
--	Desc:	提供一组常用函数，以及对 Lua 标准库的扩展
--------------------------------------------------------------------------------

-- 重写打印函数,增加调用栈
function print(...)
    if not me.LogEnable then
        return
    end
    
    local text = ""

    local index = 0
    local params = {...}
    for k,v in pairs(params) do
        index = index + 1
        if k ~= index then
            for i=index, k - 1 do
                text = text .. "nil "
                index = index + 1
            end
        end

        text = text .. tostring(v) .. " "
    end

    text = text .. debug.traceback("", 2)
    Debuger.Log(text)
end

--错误专用(红色，用于提示错误)
function printErrorRed(...)
    if not me.LogEnable then
        return
    end
    basePrint("red", ...)
end

function dumpCL(dumpObj, dumpLevel, ...)
    if not me.LogEnable then
        return
    end
    basePrint("#00DFFFFF", ...)
    if dumpObj then
        dump(dumpObj, "data_CL", dumpLevel or 5)
    end
end

function basePrint(color, ...)
    if not me.LogEnable then
        return
    end
    
    local text = string.format("<color=%s>", color)
    local index = 0
    local params = {...}
    for k,v in pairs(params) do
        index = index + 1
        if k ~= index then
            for i=index, k - 1 do
                text = text .. "nil "
                index = index + 1
            end
        end
        text = text .. tostring(v) .. " "
    end
    text = text.."</color>"
    --text = text .. debug.traceback("", 3)
    -- Debuger.LogWarning(text)
    Debuger.Log(text)
end

--[[--

输出格式化字符串

~~~ lua

printf("The value = %d", 100)

~~~

@param string fmt 输出格式
@param [mixed ...] 更多参数

]]
function printf(fmt, ...)
    print(string.format(tostring(fmt), ...))
end

-- 打印16进制
function printx(value)
    print("0x" .. string.upper(tostring(bit.tohex(value))))
end

-- 打印信息
function printInfo(fmt, ...)
    printf(fmt, ...)
end

-- 打印警告信息
function printWarning(fmt, ...)
    local text = string.format(tostring(fmt), ...)
    text = text .. debug.traceback("", 2)

    Debuger.LogWarning(text)
end

-- 打印错误信息
function printError(fmt, ...)
    local text = string.format(tostring(fmt), ...)
    text = text .. debug.traceback("", 2)

    Debuger.LogError(text)
end

--[[--

检查并尝试转换为数值，如果无法转换则返回 0

@param mixed value 要检查的值
@param [integer base] 进制，默认为十进制

@return number

]]
function checknumber(value, base)
    return tonumber(value, base) or 0
end

--[[--

检查并尝试转换为整数，如果无法转换则返回 0

@param mixed value 要检查的值

@return integer

]]
function checkint(value)
    return math.floor(checknumber(value))
end

--[[--

检查并尝试转换为浮点数(浮点数位数可选)，如果无法转换则返回 0

@param mixed value 要检查的值

@return integer

]]
function checkfloat(value, floatLength)
    floatLength = floatLength or 0
    local multi = math.pow(10, floatLength)
    return checkint(value*multi)/multi
end

--[[--

检查并尝试转换为布尔值，除了 nil 和 false，其他任何值都会返回 true

@param mixed value 要检查的值

@return boolean

]]
function checkbool(value)
    return (value ~= nil and value ~= false)
end

--[[--

检查值是否是一个表格，如果不是则返回一个空表格

@param mixed value 要检查的值

@return table

]]
function checktable(value)
    if type(value) ~= "table" then value = {} end
    return value
end

--[[--

如果表格中指定 key 的值为 nil，或者输入值不是表格，返回 false，否则返回 true

@param table hashtable 要检查的表格
@param mixed key 要检查的键名

@return boolean

]]
function isset(hashtable, key)
    local t = type(hashtable)
    return (t == "table" or t == "userdata") and hashtable[key] ~= nil
end

--[[--

深度克隆一个值

~~~ lua

-- 下面的代码，t2 是 t1 的引用，修改 t2 的属性时，t1 的内容也会发生变化
local t1 = {a = 1, b = 2}
local t2 = t1
t2.b = 3    -- t1 = {a = 1, b = 3} <-- t1.b 发生变化

-- clone() 返回 t1 的副本，修改 t2 不会影响 t1
local t1 = {a = 1, b = 2}
local t2 = clone(t1)
t2.b = 3    -- t1 = {a = 1, b = 2} <-- t1.b 不受影响

~~~

@param mixed object 要克隆的值

@return mixed

]]
function clone(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for key, value in pairs(object) do
            new_table[_copy(key)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end

--[[--

创建一个类

~~~ lua

-- 定义名为 Shape 的基础类
local Shape = class("Shape")

-- ctor() 是类的构造函数，在调用 Shape.new() 创建 Shape 对象实例时会自动执行
function Shape:ctor(shapeName)
    self.shapeName = shapeName
    printf("Shape:ctor(%s)", self.shapeName)
end

-- 为 Shape 定义个名为 draw() 的方法
function Shape:draw()
    printf("draw %s", self.shapeName)
end

--

-- Circle 是 Shape 的继承类
local Circle = class("Circle", Shape)

function Circle:ctor()
    -- 如果继承类覆盖了 ctor() 构造函数，那么必须手动调用父类构造函数
    -- 类名.super 可以访问指定类的父类
    Circle.super.ctor(self, "circle")
    self.radius = 100
end

function Circle:setRadius(radius)
    self.radius = radius
end

-- 覆盖父类的同名方法
function Circle:draw()
    printf("draw %s, raidus = %0.2f", self.shapeName, self.raidus)
end

--

local Rectangle = class("Rectangle", Shape)

function Rectangle:ctor()
    Rectangle.super.ctor(self, "rectangle")
end

--

local circle = Circle.new()             -- 输出: Shape:ctor(circle)
circle:setRaidus(200)
circle:draw()                           -- 输出: draw circle, radius = 200.00

local rectangle = Rectangle.new()       -- 输出: Shape:ctor(rectangle)
rectangle:draw()                        -- 输出: draw rectangle

~~~

### 高级用法

class() 除了定义纯 Lua 类之外，还可以从 C++ 对象继承类。

比如需要创建一个工具栏，并在添加按钮时自动排列已有的按钮，那么我们可以使用如下的代码：

~~~ lua

-- 从 CCNode 对象派生 Toolbar 类，该类具有 CCNode 的所有属性和行为
local Toolbar = class("Toolbar", function()
    return display.newNode() -- 返回一个 CCNode 对象
end)

-- 构造函数
function Toolbar:ctor()
    self.buttons = {} -- 用一个 table 来记录所有的按钮
end

-- 添加一个按钮，并且自动设置按钮位置
function Toolbar:addButton(button)
    -- 将按钮对象加入 table
    self.buttons[#self.buttons + 1] = button

    -- 添加按钮对象到 CCNode 中，以便显示该按钮
    -- 因为 Toolbar 是从 CCNode 继承的，所以可以使用 addChild() 方法
    self:addChild(button)

    -- 按照按钮数量，调整所有按钮的位置
    local x = 0
    for _, button in ipairs(self.buttons) do
        button:setPosition(x, 0)
        -- 依次排列按钮，每个按钮之间间隔 10 点
        x = x + button:getContentSize().width + 10
    end
end

~~~

class() 的这种用法让我们可以在 C++ 对象基础上任意扩展行为。

既然是继承，自然就可以覆盖 C++ 对象的方法：

~~~ lua

function Toolbar:setPosition(x, y)
    -- 由于在 Toolbar 继承类中覆盖了 CCNode 对象的 setPosition() 方法
    -- 所以我们要用以下形式才能调用到 CCNode 原本的 setPosition() 方法
    getmetatable(self).setPosition(self, x, y)

    printf("x = %0.2f, y = %0.2f", x, y)
end

~~~

**注意:** Lua 继承类覆盖的方法并不能从 C++ 调用到。也就是说通过 C++ 代码调用这个 CCNode 对象的 setPosition() 方法时，并不会执行我们在 Lua 中定义的 Toolbar:setPosition() 方法。

@param string classname 类名
@param [mixed super] 父类或者创建对象实例的函数

@return table

]]
function class(classname, super)
    local superType = type(super)
    local cls

    if superType ~= "function" and superType ~= "table" then
        superType = nil
        super = nil
    end

    if superType == "function" or (super and super.__ctype == 1) then
        -- inherited from native C++ Object
        cls = {}

        if superType == "table" then
            -- copy fields from super
            for k,v in pairs(super) do cls[k] = v end
            cls.__create = super.__create
            cls.super    = super
        else
            cls.__create = super
            cls.ctor = function() end
        end

        cls.__cname = classname
        cls.__ctype = 1

        function cls.new(...)
            local instance = cls.__create(...)
            -- copy fields from class to native object
            for k,v in pairs(cls) do instance[k] = v end
            instance.class = cls
            instance:ctor(...)
            return instance
        end

    else
        -- inherited from Lua Object
        if super then
            cls = {}
            setmetatable(cls, {__index = super})
            cls.super = super
        else
            cls = {ctor = function() end}
        end

        cls.__cname = classname
        cls.__ctype = 2 -- lua
        cls.__index = cls

        function cls.new(...)
            local instance = setmetatable({}, cls)
            instance.class = cls
            instance:ctor(...)
            return instance
        end
    end

    return cls
end

-- 提供假名以避免和 moonscript 发生冲突
function quick_class(classname, super)
	return class(classname, super)
end


--[[--

如果对象是指定类或其子类的实例，返回 true，否则返回 false

~~~ lua

local Animal = class("Animal")
local Duck = class("Duck", Animal)

print(iskindof(Duck.new(), "Animal")) -- 输出 true

~~~

@param mixed obj 要检查的对象
@param string classname 类名

@return boolean

]]
function iskindof(obj, classname)
    local t = type(obj)
    local mt
    if t == "table" then
        mt = getmetatable(obj)
    elseif t == "userdata" then
        --mt = tolua.getpeer(obj)
    end

    while mt do
        if mt.__cname == classname then
            return true
        end
        mt = mt.super
    end

    return false
end

--[[--

载入一个模块

import() 与 require() 功能相同，但具有一定程度的自动化特性。

假设我们有如下的目录结构：

~~~

app/
app/classes/
app/classes/MyClass.lua
app/classes/MyClassBase.lua
app/classes/data/Data1.lua
app/classes/data/Data2.lua

~~~

MyClass 中需要载入 MyClassBase 和 MyClassData。如果用 require()，MyClass 内的代码如下：

~~~ lua

local MyClassBase = require("app.classes.MyClassBase")
local MyClass = class("MyClass", MyClassBase)

local Data1 = require("app.classes.data.Data1")
local Data2 = require("app.classes.data.Data2")

~~~

假如我们将 MyClass 及其相关文件换一个目录存放，那么就必须修改 MyClass 中的 require() 命令，否则将找不到模块文件。

而使用 import()，我们只需要如下写：

~~~ lua

local MyClassBase = import(".MyClassBase")
local MyClass = class("MyClass", MyClassBase)

local Data1 = import(".data.Data1")
local Data2 = import(".data.Data2")

~~~

当在模块名前面有一个"." 时，import() 会从当前模块所在目录中查找其他模块。因此 MyClass 及其相关文件不管存放到什么目录里，我们都不再需要修改 MyClass 中的 import() 命令。这在开发一些重复使用的功能组件时，会非常方便。

我们可以在模块名前添加多个"." ，这样 import() 会从更上层的目录开始查找模块。

~

不过 import() 只有在模块级别调用（也就是没有将 import() 写在任何函数中）时，才能够自动得到当前模块名。如果需要在函数中调用 import()，那么就需要指定当前模块名：

~~~ lua

# MyClass.lua

# 这里的 ... 是隐藏参数，包含了当前模块的名字，所以最好将这行代码写在模块的第一行
local CURRENT_MODULE_NAME = ...

local function testLoad()
    local MyClassBase = import(".MyClassBase", CURRENT_MODULE_NAME)
    # 更多代码
end

~~~

@param string moduleName 要载入的模块的名字
@param [string currentModuleName] 当前模块名

@return module

]]
function import(moduleName, currentModuleName)
    local currentModuleNameParts
    local moduleFullName = moduleName
    local offset = 1

    while true do
        if string.byte(moduleName, offset) ~= 46 then -- .
            moduleFullName = string.sub(moduleName, offset)
            if currentModuleNameParts and #currentModuleNameParts > 0 then
                moduleFullName = table.concat(currentModuleNameParts, ".") .. "." .. moduleFullName
            end
            break
        end
        offset = offset + 1

        if not currentModuleNameParts then
            if not currentModuleName then
                local n,v = debug.getlocal(3, 1)
                currentModuleName = v
            end

            currentModuleNameParts = string.split(currentModuleName, ".")
        end
        table.remove(currentModuleNameParts, #currentModuleNameParts)
    end

    return require(moduleFullName)
end

-- 导入表
function importData(name)
    return require("game.logicModule.data." .. name)
end

-- 卸载表
function unimportData(name)
    package.loaded["game.logicModule.data." .. name] = nil
end

--延迟导入表,只有真正使用到表的时候才会被require
function delayImport(target, field, file)
    if target.__delayTable then
        if target.__delayTable[field] == nil then
            target.__delayTable[field] = file
        end
        return
    end

    target.__delayTable = {[field] = file}
    local mt = getmetatable(target) or {__index = {}}
    local meta = {
        __index = function(t, k)
            local file = t.__delayTable[k]
            if file then
                t[k] = importData(file)
                t.__delayTable[k] = nil
                return t[k]
            end
            return mt.__index[k]
        end,
    }
    setmetatable(target, meta)
end


--[[--

将 Lua 对象及其方法包装为一个匿名函数

在 quick-cocos2d-x 中，许多功能需要传入一个 Lua 函数做参数，然后在特定事件发生时就会调用传入的函数。例如触摸事件、帧事件等等。

~~~ lua

local MyScene = class("MyScene", function()
    return display.newScene("MyScene")
end)

function MyScene:ctor()
    self.frameTimeCount = 0
    -- 注册帧事件
    self:addEventListener(cc.ENTER_FRAME_EVENT, self.onEnterFrame)
end

function MyScene:onEnterFrame(dt)
    self.frameTimeCount = self.frameTimeCount + dt
end

~~~

上述代码执行时将出错，报告"Invalid self" ，这就是因为 C++ 无法识别 Lua 对象方法。因此在调用我们传入的 self.onEnterFrame 方法时没有提供正确的参数。

要让上述的代码正常工作，就需要使用 handler() 进行一下包装：

~~~ lua

function MyScene:ctor()
    self.frameTimeCount = 0
    -- 注册帧事件
    self:addEventListener(cc.ENTER_FRAME_EVENT, handler(self, self.onEnterFrame))
end

~~~

实际上，除了 C++ 回调 Lua 函数之外，在其他所有需要回调的地方都可以使用 handler()。

@param mixed obj Lua 对象
@param function method 对象方法

@return function

]]
function handler(obj, method)
    return function(...)
        if not method then
            dump(obj, "handler数据")
        end

        return method(obj, ...)
    end
end


function handler_args(obj, method, ...)
	local args = {...}
    return function(...)
        local temp = {}
        
        table.insertto(temp, {...})
        table.insertto(temp, args)
        return method(obj, unpack(temp))
    end
end

function method_args(method, ...)
    local args = {...}
    return function(...)
        local temp = {}

        table.insertto(temp, {...})
        table.insertto(temp, args)
        return method(unpack(temp))
    end
end

--[[--

根据系统时间初始化随机数种子，让后续的 math.random() 返回更随机的值

]]
function math.newrandomseed()
    local ok, socket = pcall(function()
        return require("socket")
    end)

    if ok then
        -- 如果集成了 socket 模块，则使用 socket.gettime() 获取随机数种子
        math.randomseed(socket.gettime() * 1000)
    else
        math.randomseed(os.time())
    end
    math.random()
    math.random()
    math.random()
    math.random()
end

--[[--

对数值进行四舍五入，如果不是数值则返回 0

@param number value 输入值

@return number

]]
function math.round(value)
    return math.floor(value + 0.5)
end

function math.angle2radian(angle)
	return angle*math.pi/180
end

function math.radian2angle(radian)
	return radian/math.pi*180
end


function math.clamp(value, min, max)
    return math.min(math.max(min, value), max)
end

function math.lerp(a, b, t)
    return a + (b - a) * math.min(math.max(0.0, t), 1.0)
end

--[[--

检查指定的文件或目录是否存在，如果存在返回 true，否则返回 false

可以使用 CCFileUtils:fullPathForFilename() 函数查找特定文件的完整路径，例如：

~~~ lua

local path = CCFileUtils:sharedFileUtils():fullPathForFilename("gamedata.txt")
if io.exists(path) then
    ....
end

~~~

@param string path 要检查的文件或目录的完全路径

@return boolean

]]
function io.exists(path)
    local file = io.open(path, "r")
    if file then
        io.Close(file)
        return true
    end
    return false
end

--[[--

读取文件内容，返回包含文件内容的字符串，如果失败返回 nil

io.readfile() 会一次性读取整个文件的内容，并返回一个字符串，因此该函数不适宜读取太大的文件。

@param string path 文件完全路径

@return string

]]
function io.readfile(path)
    local file = io.open(path, "r")
    if file then
        local content = file:read("*a")
        io.Close(file)
        return content
    end
    return nil
end

--[[--

以字符串内容写入文件，成功返回 true，失败返回 false

"mode 写入模式" 参数决定 io.writefile() 如何写入内容，可用的值如下：

-   "w+" : 覆盖文件已有内容，如果文件不存在则创建新文件
-   "a+" : 追加内容到文件尾部，如果文件不存在则创建文件

此外，还可以在 "写入模式" 参数最后追加字符 "b" ，表示以二进制方式写入数据，这样可以避免内容写入不完整。

**Android 特别提示:** 在 Android 平台上，文件只能写入存储卡所在路径，assets 和 data 等目录都是无法写入的。

@param string path 文件完全路径
@param string content 要写入的内容
@param [string mode] 写入模式，默认值为 "w+b"

@return boolean

]]
function io.writefile(path, content, mode)
    mode = mode or "w+b"
    local file = io.open(path, mode)
    if file then
        if file:write(content) == nil then return false end
        io.Close(file)
        return true
    else
        return false
    end
end

--[[--

拆分一个路径字符串，返回组成路径的各个部分

~~~ lua

local pathinfo  = io.pathinfo("/var/app/test/abc")

-- 结果:
-- pathinfo.dirname  = "/var/app/test/"
-- pathinfo.filename = "abc"
-- pathinfo.basename = "abc"

~~~

@param string path 要分拆的路径字符串

@return table

]]
function io.pathinfo(path)
    local pos = string.len(path)
    local extpos = pos + 1
    while pos > 0 do
        local b = string.byte(path, pos)
        if b == 46 then -- 46 = char "."
            extpos = pos
        elseif b == 47 then -- 47 = char "/"
            break
        end
        pos = pos - 1
    end

    local dirname = string.sub(path, 1, pos)
    local filename = string.sub(path, pos + 1)
    extpos = extpos - pos
    local basename = string.sub(filename, 1, extpos - 1)
    local extname = string.sub(filename, extpos)
    return {
        dirname = dirname,
        filename = filename,
        basename = basename,
        extname = extname
    }
end

--[[--

返回指定文件的大小，如果失败返回 false

@param string path 文件完全路径

@return integer

]]
function io.filesize(path)
    local size = false
    local file = io.open(path, "r")
    if file then
        local current = file:seek()
        size = file:seek("end")
        file:seek("set", current)
        io.Close(file)
    end
    return size
end

--[[--

计算表格包含的字段数量

Lua table 的 "#" 操作只对依次排序的数值下标数组有效，table.nums() 则计算 table 中所有不为 nil 的值的个数。

@param table t 要检查的表格

@return integer

]]
function table.nums(t)
    local count = 0
    for k, v in pairs(t) do
        count = count + 1
    end
    return count
end

--[[--

返回指定表格中的所有键

~~~ lua

local hashtable = {a = 1, b = 2, c = 3}
local keys = table.keys(hashtable)
-- keys = {"a", "b", "c"}

~~~

@param table hashtable 要检查的表格

@return table

]]
function table.keys(hashtable)
    local keys = {}
    for k, v in pairs(hashtable) do
        keys[#keys + 1] = k
    end
    return keys
end

--[[--

返回指定表格中的所有值

~~~ lua

local hashtable = {a = 1, b = 2, c = 3}
local values = table.values(hashtable)
-- values = {1, 2, 3}

~~~

@param table hashtable 要检查的表格

@return table

]]
function table.values(hashtable)
    local values = {}
    for k, v in pairs(hashtable) do
        values[#values + 1] = v
    end
    return values
end

--[[--

将来源表格中所有键及其值复制到目标表格对象中，如果存在同名键，则覆盖其值

~~~ lua

local dest = {a = 1, b = 2}
local src  = {c = 3, d = 4}
table.merge(dest, src)
-- dest = {a = 1, b = 2, c = 3, d = 4}

~~~

@param table dest 目标表格
@param table src 来源表格

]]
function table.merge(dest, src)
    for k, v in pairs(src) do
        dest[k] = v
    end
end

--深度合并
function table.deepMerge(dest, src)
    for k, v in pairs(src) do
        if type(v) == "table" then
            if not dest[k] then
                dest[k] = v
            elseif type(dest[k]) == "table" and k ~= "class" then
                table.deepMerge(dest[k], v)
            end
        else
            dest[k] = v
        end
    end
end

-- ch 分隔符
function table.toString(src, ch)
    if not src or #src <= 0 then
        return ""
    end
    ch = ch or ","
    local str = tostring(src[1])
    for i=2,#src do
        str = str .. ch .. src[i]
    end
    return str
end

--[[--

在目标表格的指定位置插入来源表格，如果没有指定位置则连接两个表格

~~~ lua

local dest = {1, 2, 3}
local src  = {4, 5, 6}
table.insertto(dest, src)
-- dest = {1, 2, 3, 4, 5, 6}

dest = {1, 2, 3}
table.insertto(dest, src, 5)
-- dest = {1, 2, 3, nil, 4, 5, 6}

~~~

@param table dest 目标表格
@param table src 来源表格
@param [integer begin] 插入位置

]]
function table.insertto(dest, src, begin)
	begin = checkint(begin)
	if begin <= 0 then
		begin = #dest + 1
	end

	local len = #src
	for i = 0, len - 1 do
		dest[i + begin] = src[i + 1]
	end
end

function table.copyrange(dest, src, begin, count)
    begin = begin or 1
    count = count or 0

    countSrc = #src
    local indexEnd = begin + count - 1
    for i=begin,indexEnd do
        if i > countSrc then
            break
        end

        table.insert(dest, src[i])
    end
end

--- 采用二分查找插入 测试代码 table_test.lua-->TestSortInsert
-- @number t: 源表，必须要有序，不然插入有问题
-- @number v: 插入的对象
-- @number f: 排序sort方法
-- @return pos,newTb 插入位置,新表
-- @usage table.binaryInsert(t, v, sort)
function table.binaryInsert(t, v, f)
    local len = #t -- length
    local left = 1
    local right = len
    while (left <= right) do
        local mid = left + math.floor((right - left) /2)
        if f(t[mid], v) then
            left = mid + 1
        elseif f(v, t[mid]) then
            right = mid - 1
        else
            return mid, table.insert(t, mid, v)
        end
    end
    return left, table.insert(t, left, v)
end

--- 采用二分查找删除 测试代码 table_test.lua
-- @number t: 源表，必须要有序，不然删除有问题
-- @number v: 删除的对象
-- @number f: 排序sort方法
-- @return pos,newTb 删除位子,新表
-- @usage table.binaryRemove(t, v, sort)
function table.binaryRemove(t, v, f)
    local len = #t -- length
    local left = 1
    local right = len
    while (left <= right) do
        local mid = left + math.floor((right - left) /2)
        if f(t[mid], v) then
            left = mid + 1
        elseif f(v, t[mid]) then
            right = mid - 1
        else
            return mid, table.remove(t, mid)
        end
    end
    return left, nil -- table.remove(t, left)
end

--- 有序table pop出第一个元素
-- @number number desc 
-- @return return desc 
-- @usage usage desc 
function table.pop(t)
    if not t or not next(t) then
        return nil
    end
    local ret = t[1]
    table.remove(t, 1)
    return ret
end


--[[

从表格中查找指定值，返回其索引，如果没找到返回 -1

~~~ lua

local array = {"a", "b", "c"}
print(table.indexof(array, "b")) -- 输出 2

~~~

@param table array 表格
@param mixed value 要查找的值
@param [integer begin] 起始索引值

@return integer

]]
function table.indexof(array, value, begin)
    for i = begin or 1, #array do
        if array[i] == value then return i end
    end
	return -1
end

--[[--

从表格中查找指定值，返回其 key，如果没找到返回 nil

~~~ lua

local hashtable = {name = "dualface", comp = "chukong"}
print(table.keyof(hashtable, "chukong")) -- 输出 comp

~~~

@param table hashtable 表格
@param mixed value 要查找的值

@return string 该值对应的 key

]]
function table.keyof(hashtable, value)
    for k, v in pairs(hashtable) do
        if v == value then return k end
    end
    return nil
end

--[[--

从表格中删除指定值，返回删除的值的个数

~~~ lua

local array = {"a", "b", "c", "c"}
print(table.removebyvalue(array, "c", true)) -- 输出 2

~~~

@param table array 表格
@param mixed value 要删除的值
@param [boolean removeall] 是否删除所有相同的值

@return integer

]]
function table.removebyvalue(array, value, removeall)
    local c, i, max = 0, 1, #array
    while i <= max do
        if array[i] == value then
            table.remove(array, i)
            c = c + 1
            i = i - 1
            max = max - 1
            if not removeall then break end
        end
        i = i + 1
    end
    return c
end

--[[--

对表格中每一个值执行一次指定的函数，并用函数返回值更新表格内容

~~~ lua

local t = {name = "dualface", comp = "chukong"}
table.map(t, function(v, k)
    -- 在每一个值前后添加括号
    return "[" .. v .. "]"
end)

-- 输出修改后的表格内容
for k, v in pairs(t) do
    print(k, v)
end

-- 输出
-- name [dualface]
-- comp [chukong]

~~~

fn 参数指定的函数具有两个参数，并且返回一个值。原型如下：

~~~ lua

function map_function(value, key)
    return value
end

~~~

@param table t 表格
@param function fn 函数

]]
function table.map(t, fn)
    for k, v in pairs(t) do
        t[k] = fn(v, k)
    end
end

--[[--

对表格中每一个值执行一次指定的函数，但不改变表格内容

~~~ lua

local t = {name = "dualface", comp = "chukong"}
table.walk(t, function(v, k)
    -- 输出每一个值
    print(v)
end)

~~~

fn 参数指定的函数具有两个参数，没有返回值。原型如下：

~~~ lua

function map_function(value, key)

end

~~~

@param table t 表格
@param function fn 函数

]]
function table.walk(t, fn)
    for k,v in pairs(t) do
        fn(v, k)
    end
end

--[[--

对表格中每一个值执行一次指定的函数，如果该函数返回 false，则对应的值会从表格中删除

~~~ lua

local t = {name = "dualface", comp = "chukong"}
table.filter(t, function(v, k)
    return v ~= "dualface" -- 当值等于 dualface 时过滤掉该值
end)

-- 输出修改后的表格内容
for k, v in pairs(t) do
    print(k, v)
end

-- 输出
-- comp chukong

~~~

fn 参数指定的函数具有两个参数，并且返回一个 boolean 值。原型如下：

~~~ lua

function map_function(value, key)
    return true or false
end

~~~

@param table t 表格
@param function fn 函数

]]
function table.filter(t, fn)
    for k, v in pairs(t) do
        if not fn(v, k) then t[k] = nil end
    end
end

--[[--

遍历表格，确保其中的值唯一

~~~ lua

local t = {"a", "a", "b", "c"} -- 重复的 a 会被过滤掉
local n = table.unique(t)

for k, v in pairs(n) do
    print(v)
end

-- 输出
-- a
-- b
-- c

~~~

@param table t 表格

@return table 包含所有唯一值的新表格

]]
function table.unique(t)
    local check = {}
    local n = {}
    for k, v in pairs(t) do
        if not check[v] then
            n[k] = v
            check[v] = true
        end
    end
    return n
end


function table.slice(t)
    local ret = {}
    for k, v in pairs(t) do
        ret[k] = v
    end
    
    return ret
end

function table.copy(ori_table, new_table)
    if type(ori_table) ~= "table" or type(new_table) ~= "table" or #new_table ~= 0 then
        return
    end

    for k,v in pairs(ori_table) do
        local vtype = type(v)
        if vtype == "table" then
            new_table[k] = {}
            table.copy(v, new_table[k])
        else
            new_table[k] = v
        end
    end
end

function table.reverse(t)
    local cloneTable = clone(t)

    for i=1,#t do
        t[i] = cloneTable[#t-i+1]
    end
end
--[[--
    倒叙返回table
]]
function table.reverseTable(tab)  
    local tmp = {}  
    for i = 1, #tab do  
        tmp[i] = table.remove(tab)  
    end  
    return tmp  
end  

--返回一个表中数组部分的随机元素
function table.getRandomElement(tab)
    if tab then
        local length = #tab
        if length > 0 then
            local index = math.random(1, length)
            return tab[index]
        end
    end
end

--截取部分元素
function table.clamp(tab, count)
    if tab then
        local tmp = {}
        local length = 0
        for k,v in pairs(tab) do
            tmp[k] = v
            length = length + 1
            if length >= count then
                break
            end
        end
        return tmp
    end
end

string._htmlspecialchars_set = {}
string._htmlspecialchars_set["&"] = "&amp;"
string._htmlspecialchars_set["\""] = "&quot;"
string._htmlspecialchars_set["'"] = "&#039;"
string._htmlspecialchars_set["<"] = "&lt;"
string._htmlspecialchars_set[">"] = "&gt;"

--[[--

将特殊字符转为 HTML 转义符

~~~ lua

print(string.htmlspecialchars("<ABC>"))
-- 输出 &lt;ABC&gt;

~~~

@param string input 输入字符串

@return string 转换结果

]]
function string.htmlspecialchars(input)
    for k, v in pairs(string._htmlspecialchars_set) do
        input = string.gsub(input, k, v)
    end
    return input
end

--[[--

将 HTML 转义符还原为特殊字符，功能与 string.htmlspecialchars() 正好相反

~~~ lua

print(string.restorehtmlspecialchars("&lt;ABC&gt;"))
-- 输出 <ABC>

~~~

@param string input 输入字符串

@return string 转换结果

]]
function string.restorehtmlspecialchars(input)
    for k, v in pairs(string._htmlspecialchars_set) do
        input = string.gsub(input, v, k)
    end
    return input
end

--[[--

将字符串中的 \n 换行符转换为 HTML 标记

~~~ lua

print(string.nl2br("Hello\nWorld"))
-- 输出
-- Hello<br />World

~~~

@param string input 输入字符串

@return string 转换结果

]]
function string.nl2br(input)
    return string.gsub(input, "\n", "<br />")
end

--[[--

将字符串中的特殊字符和 \n 换行符转换为 HTML 转移符和标记

~~~ lua

print(string.nl2br("<Hello>\nWorld"))
-- 输出
-- &lt;Hello&gt;<br />World

~~~

@param string input 输入字符串

@return string 转换结果

]]
function string.text2html(input)
    input = string.gsub(input, "\t", "    ")
    input = string.htmlspecialchars(input)
    input = string.gsub(input, " ", "&nbsp;")
    input = string.nl2br(input)
    return input
end

--[[--

用指定字符或字符串分割输入字符串，返回包含分割结果的数组

~~~ lua

local input = "Hello,World"
local res = string.split(input, ",")
-- res = {"Hello", "World"}

local input = "Hello-+-World-+-Quick"
local res = string.split(input, "-+-")
-- res = {"Hello", "World", "Quick"}

~~~

@param string input 输入字符串
@param string delimiter 分割标记字符或字符串

@return array 包含分割结果的数组

]]
function string.split(input, delimiter)
    input = tostring(input)
    delimiter = tostring(delimiter)
    if (delimiter=='') then return false end
    local pos,arr = 0, {}
    -- for each divider found
    for st,sp in function() return string.find(input, delimiter, pos, true) end do
        table.insert(arr, string.sub(input, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(input, pos))
    return arr
end

--[[--

去除输入字符串头部的空白字符，返回结果

~~~ lua

local input = "  ABC"
print(string.ltrim(input))
-- 输出 ABC，输入字符串前面的两个空格被去掉了

~~~

空白字符包括：

-   空格
-   制表符 \t
-   换行符 \n
-   回到行首符 \r

@param string input 输入字符串

@return string 结果

@see string.rtrim, string.trim

]]
function string.ltrim(input)
    return string.gsub(input, "^[ \t\n\r]+", "")
end

--[[--

去除输入字符串尾部的空白字符，返回结果

~~~ lua

local input = "ABC  "
print(string.ltrim(input))
-- 输出 ABC，输入字符串最后的两个空格被去掉了

~~~

@param string input 输入字符串

@return string 结果

@see string.ltrim, string.trim

]]
function string.rtrim(input)
    return string.gsub(input, "[ \t\n\r]+$", "")
end

--[[--

去掉字符串首尾的空白字符，返回结果

@param string input 输入字符串

@return string 结果

@see string.ltrim, string.rtrim

]]
function string.trim(input)
    input = string.gsub(input, "^[ \t\n\r]+", "")
    return string.gsub(input, "[ \t\n\r]+$", "")
end

--[[--

将字符串的第一个字符转为大写，返回结果

~~~ lua

local input = "hello"
print(string.ucfirst(input))
-- 输出 Hello

~~~

@param string input 输入字符串

@return string 结果

]]
function string.ucfirst(input)
    return string.upper(string.sub(input, 1, 1)) .. string.sub(input, 2)
end

local function urlencodechar(char)
    return "%" .. string.format("%02X", string.byte(char))
end

--[[--

将字符串转换为符合 URL 传递要求的格式，并返回转换结果

~~~ lua

local input = "hello world"
print(string.urlencode(input))
-- 输出
-- hello%20world

~~~

@param string input 输入字符串

@return string 转换后的结果

@see string.urldecode

]]
function string.urlencode(input)
    -- convert line endings
    input = string.gsub(tostring(input), "\n", "\r\n")
    -- escape all characters but alphanumeric, '.' and '-'
    input = string.gsub(input, "([^%w%.%- ])", urlencodechar)
    -- convert spaces to "+" symbols
    -- return string.gsub(input, " ", "+")
    return string.gsub(input, " ", "%%20")
end

--[[--

将 URL 中的特殊字符还原，并返回结果

~~~ lua

local input = "hello%20world"
print(string.urldecode(input))
-- 输出
-- hello world

~~~

@param string input 输入字符串

@return string 转换后的结果

@see string.urlencode

]]
function string.urldecode(input)
    -- input = string.gsub (input, "+", " ")
    input = string.gsub (input, "%%20", " ")
    input = string.gsub (input, "%%(%x%x)", function(h) return string.char(checknumber(h,16)) end)
    input = string.gsub (input, "\r\n", "\n")
    return input
end

--[[--

计算 UTF8 字符串的长度，每一个中文算一个字符

~~~ lua

local input = "你好World"
print(string.utf8len(input))
-- 输出 7

~~~

@param string input 输入字符串

@return integer 长度

]]
function string.utf8len(input)
    local len  = string.len(input)
    local left = len
    local cnt  = 0
    local arr  = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}
    while left ~= 0 do
        local tmp = string.byte(input, -left)
        local i   = #arr
        while arr[i] do
            if tmp >= arr[i] then
                left = left - i
                break
            end
            i = i - 1
        end
        cnt = cnt + 1
    end
    return cnt
end

--[[--

将数值格式化为包含千分位分隔符的字符串

~~~ lua

print(string.formatnumberthousands(1924235))
-- 输出 1,924,235

~~~

@param number num 数值

@return string 格式化结果

]]
function string.formatnumberthousands(num)
    local formatted = tostring(checknumber(num))
    local k
    while true do
        formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
        if k == 0 then break end
    end
    return formatted
end

-- 字符串转Point
function string.toPoint(value)
    value = value or "0,0"
    local ret = {x = 0, y = 0}

    local nums = string.split(value, ",")
    if #nums == 2 then
        ret.x = checknumber(nums[1])
        ret.y = checknumber(nums[2])
    end

    return ret
end

-- 字符串转SexyColor
function string:toSexyColor(str)
	local value = checkint("0x" .. str)

	local data = {}
	while value ~= 0 do
		table.insert(data, value % 256)
		value = checkint(value / 256)
	end

	local ret = SexyColor()

	local index = 0
	if string.len(str) > 6 then
		index = 1
		ret.alpha = data[1] or 255
	end

	ret.blue 	= data[index + 1] or 0
	ret.green 	= data[index + 2] or 0
	ret.red 	= data[index + 3] or 0

	return ret
end

-- 判断utf8字符byte长度
-- 0xxxxxxx - 1 byte
-- 110yxxxx - 192, 2 byte
-- 1110yyyy - 225, 3 byte
-- 11110zzz - 240, 4 byte
local function chsize(char)
    if not char then
        print("not char")
        return 0
    elseif char > 240 then
        return 4
    elseif char > 225 then
        return 3
    elseif char > 192 then
        return 2
    else
        return 1
    end
end

-- 截取utf8 字符串
-- str:         要截取的字符串
-- startChar:   开始字符下标,从1开始
-- numChars:    要截取的字符长度
function string.utf8sub(str, startChar, numChars)
    local startIndex = 1
    while startChar > 1 do
        local char = string.byte(str, startIndex)
        startIndex = startIndex + chsize(char)
        startChar = startChar - 1
    end

    local currentIndex = startIndex

    while numChars > 0 and currentIndex <= #str do
        local char = string.byte(str, currentIndex)
        currentIndex = currentIndex + chsize(char)
        numChars = numChars -1
    end
    return str:sub(startIndex, currentIndex - 1)
end


function requireComponent(target, go)
    return me.component[target].new(go)
end

--yesBtnTitle: nil时默认为 ["common_text_204"] = "确定",
function SoraDShowMsgBox(showMsg,yesBtnTitle,noBtnTitle,callBackHandle, dayCheck)  
    yesBtnTitle = yesBtnTitle or i18n("common_text_204") 
    local _data
    if not noBtnTitle then
       _data = {
            describe = showMsg,
            buttons = {
                {
                    btnTxt = yesBtnTitle,
                    func = callBackHandle
                }
            },
       }    
    else
        _data = {
            describe = showMsg,
            buttons = {
                {
                    btnTxt = yesBtnTitle,
                    func = callBackHandle
                },
                {
                    btnTxt = noBtnTitle,
                }
            },         
        }
    end

    if dayCheck ~= nil then
        table.merge(_data, {button = {func = callBackHandle} ,dayCheck = dayCheck})
    end
    CommonPopupTips.Create(GPopupTipStyleType.Normal, _data)    
end

function SoraDConvertNumberWithUnit(num)
    Util.ConvertNumberWithUnit(num)
end

function SoraDchangeToMap( list )
    if type(list) ~= "table" then
        print("list is not a table value !")
        return false,list
    elseif #list%2 ~= 0 then
        print("list is invalid !")
        return false,list
    end
    local data = {}
    local index = 1
    for i=1,#list-1,2 do
        data[index] = {key = list[i],value = list[i+1]}
        index = index + 1
    end
    return true,data
end

function isServer()
    return false
end
--[[
lua table内存检测释放
]]
function SoraCollectgarbage(target)
    local preGarbage = collectgarbage("count")
    collectgarbage("collect")
    local laterGarbage = collectgarbage("count")

    --print(string.format("内存回收: %s, 回收前: %d, 回收后: %d", tostring(target), preGarbage, laterGarbage))
end

--[[
加密属性
对指定数值属性进行内存数值混淆，使外部程序无法获得真实数值
bit.bswap(field)位运算会导致浮点型丢失小数位，故使用简单乘除对其混淆

--方式1：继承自ObjectBase(推荐)
self:AddEncryptField("age", 18)
print(self.age)--输出：18

--方式2：继承自ObjectBase
EncryptFields(self,{age = 18})
print(self.age)--输出：18

--方式3：
local target = {}
EncryptFields(target,{age = 18})
print(target.age)--输出：18
]]
function EncryptFields(target, fields)
    --添加新属性
    local __fields = target.__fields
    if __fields then
        for k,v in pairs(fields) do
            __fields[k] = v - target.__factor
        end
        return
    end

    --生成混淆因子
    if not target.__factor then
        target.__factor = math.random(1111,9999)
    end

    --初始数值混淆
    for k,v in pairs(fields) do
        fields[k] = v - target.__factor
    end
    
    --生成属性元表
    target.__fields = fields
    local mt = getmetatable(target) or {__index = {}}
    local meta = {
        __index = function(t, k)
            if target.__fields[k] then
                return target.__fields[k] + target.__factor
            end
            return mt.__index[k]
        end,
        __newindex = function(t, k, v)
            if target.__fields[k] then
                target.__fields[k] = v - target.__factor
            else
                mt.__index[k] = v
            end
        end,
    }
    setmetatable(target, meta)
end

--[[
加密原属性值
拷贝对象属性值到加密属性表,并删除原属性

--方式4：
local target = {age = 18, height = 170}
EncryptKeys(target, {"age"})
结果：
target = {height = 170, __fields = {age = xx(18加密后)}}
print(target.age)--输出：18
]]
function EncryptKeys(target, keys)
    if target == nil then return end

    --默认加密所有数值属性
    if keys == nil or #keys == 0 then
        keys = {}
        for k,v in pairs(target) do
            if type(v) == "number" then
                table.insert(k)
            end
        end
    end

    --加密指定属性
    local fields = {}
    for i,v in ipairs(keys) do
        local value = target[v]
        if value then
            target[v] = nil
            if type(value) ~= "number" then
                value = 0
            end
        end
        fields[v] = value or 0
    end
    
    if table.nums(fields) > 0 then
        EncryptFields(target, fields)
    end
end